import { checkOssClient, initOssClient } from '@/utils/upload-oss'
import Vue from 'vue'
import { getStsToken } from '@/api/component/oss/oss'
import { mapGetters } from 'vuex'

function OSS(options) {
  const defaultOptions = {
    ossConfig: { region: '', bucket: '', fileRoot: '' },
    // oss文件存储路径
    pathOssOnline: 'temp/',
    // 是否上传图片
    isUpImg: true,
    // 限制文件大小
    limitFileSize: 1024 * 1024,
    // 限制上传文件类型
    limitExtList: ['jpg', 'jpeg', 'png', 'gif'],
    // 错误提示
    msg: {
      limitSize: '文件大小超过限制',
      limitExt: '文件类型不支持上传'
    }
  }
  options = mergeOptions(defaultOptions, options)
  const data = {
    ...options,
    // 文件列表
    fileList: [],
    // 上传文件列表
    fileOptList: {}
  }
  const methods = {
    notify(title, type = 'info') {
      oss.vms[0].vm.$notify({
        title,
        type,
        duration: 2500
      })
    },
    // 获取ossClient
    async getClient() {
      let client = checkOssClient()
      console.info('获取ossClient:' + client)
      if (client === null) {
        console.info('获取ossClient为空，准备重新拉取')

        const data = await getStsToken()

        console.info('最新stsToken数据(如下):')
        console.info(data)

        client = initOssClient(data.accessKeyId, data.accessKeySecret, data.securityToken, data.endTime)
      }
      return client
    },
    // 异步上传
    async upload(opt) {
      console.info('准备上传')
      const file = opt.file
      const uid = file.uid

      // 获取client
      const client = await oss.getClient()

      // 获取文件路径
      const path = oss.ossConfig.fileRoot + oss.pathOssOnline + getRandomFileName(getFileExt(file.name))
      try {
        oss.fileOptList[uid].status = OSS.STATUS.PROGRESSING
        oss.fileOptList[uid].tip = '图片上传中'

        const config = {
          progress: async function(p) {
            // 上传进度条，el-upload 组件自带的方法
            opt.onProgress({ percent: p * 100 })
          }
        }
        if (oss.isUpImg) {
          config['mime'] = 'image/jpg'
        }
        // 使用 multipartUpload 正式上传到 oss
        const res = await client.multipartUpload(path, file, config)

        // 去除 oss 分片上传后返回所带的查询参数，否则访问会 403
        // 替换协议，统一使用 'https://'，否则 Android 无法显示图片
        const ossPath = res.res.requestUrls[0].split('?')[0].replace('http://', 'https://')

        opt.onSuccess({ visitPath: ossPath, relativePath: path })
      } catch (error) {
        opt.onError('上传异常')
      }
    },
    // 上传处理之前
    handleBeforeUpload(file) {
      const fileName = file.name
      const fileSize = file.size

      // 文件支持检查
      if (!isFileValid(oss.limitExtList, getFileExt(fileName))) {
        oss.notify(oss.msg.limitExt, 'warning')

        return false
      }

      // 文件大小检查
      if (fileSize > oss.limitFileSize) {
        oss.notify(oss.msg.limitSize, 'warning')

        return false
      }

      console.info('上传处理检查')

      const uid = file.uid
      oss.fileOptList[uid] = { file: file, uid: uid, status: OSS.STATUS.READY, tip: '图片准备上传' }

      return true
    },
    // 处理删除
    handleRemove(file) {
      console.info('处理删除')
      const uid = file.uid
      delete oss.fileOptList[uid]
    },
    // 处理返回成功结果
    handleSuccess(response, file, fileList) {
      if (!response) {
        return
      }
      console.info('处理上传成功')

      oss.notify('上传成功', 'success')

      const uid = file.uid
      oss.fileOptList[uid].visitPath = response.visitPath
      oss.fileOptList[uid].relativePath = response.relativePath
      oss.fileOptList[uid].status = OSS.STATUS.SUCCESS
      oss.fileOptList[uid].tip = '图片上传成功'

      callVmHook(oss, OSS.HOOK.uploadSuccess, oss.fileOptList[uid])
    },
    // 处理返回失败结果
    handleError(error, file, fileList) {
      console.info('处理上传失败')

      oss.notify(error, 'warning')

      const uid = file.uid
      oss.fileOptList[uid].tip = error
      oss.fileOptList[uid].status = OSS.STATUS.FAIL

      callVmHook(oss, OSS.HOOK.uploadError, oss.fileOptList[uid])
    },
    // 检查所有文件是否上传成功
    checkAllSuccess() {
      return Object.keys(oss.fileOptList).every(item => oss.fileOptList[item].status === OSS.STATUS.SUCCESS)
    },
    // 重置列表
    resetChoose() {
      oss.fileOptList = {}
      oss.fileList = []
    }
  }

  const oss = Object.assign({}, data)
  // 可观测化
  Vue.observable(oss)
  // 附加方法
  Object.assign(oss, methods)
  // 记录初始默认的查询参数，后续重置查询时使用
  Object.assign(oss, {
    // 预留4位存储：组件 调起，调试查看也方便找
    vms: Array(1),
    /**
     * 注册组件实例
     * @param {String} type 类型
     * @param {*} vm 组件实例
     * @param {Number} index 该参数内部使用
     */
    registerVM(type, vm, index = -1) {
      const vmObj = {
        type,
        vm: vm
      }
      if (index < 0) {
        this.vms.push(vmObj)
        return
      }
      this.vms.length = Math.max(this.vms.length, index)
      this.vms.splice(index, 1, vmObj)
    },
    /**
     * 取消注册组件实例
     * @param {*} vm 组件实例
     */
    unregisterVM(vm) {
      this.vms.splice(this.vms.findIndex(e => e && e.vm === vm), 1)
    }
  })
  // 冻结处理，需要扩展数据的话，使用crud.updateProp(name, value)，以crud.props.name形式访问，这个是响应式的，可以做数据绑定
  Object.freeze(oss)

  return oss
}

function presenter(oss) {
  return {
    inject: {
      oss: {
        from: 'oss'
      }
    },
    beforeCreate() {
      // 由于initInjections在initProvide之前执行，如果该组件自己就需要crud，需要在initInjections前准备好crud
      this._provided = { oss }
    },
    data() {
      return {}
    },
    computed: {
      ...mapGetters([
        'ossRegion',
        'ossBucket',
        'ossFileRoot'
      ])
    },
    created() {
      this.oss.registerVM('presenter', this, 0)

      this.oss.ossConfig = { region: this.ossRegion, bucket: this.ossBucket, fileRoot: this.ossFileRoot }
    },
    beforeDestroy() {
      this.oss.unregisterVM(this)
    },
    mounted() {}
  }
}

OSS.STATUS = {
  READY: 0,
  PROGRESSING: 1,
  SUCCESS: 2,
  FAIL: 3
}

// 合并配置项
function mergeOptions(src, opts) {
  const optsRet = {
    ...src
  }
  for (const key in src) {
    if (opts.hasOwnProperty(key)) {
      optsRet[key] = opts[key]
    }
  }
  return optsRet
}

// hook VM
function callVmHook(oss, hook) {
  let ret = true
  const nargs = [oss]
  for (let i = 2; i < arguments.length; ++i) {
    nargs.push(arguments[i])
  }
  // 有些组件扮演了多个角色，调用钩子时，需要去重
  const vmSet = new Set()
  oss.vms.forEach(vm => vm && vmSet.add(vm.vm))
  vmSet.forEach(vm => {
    if (vm[hook]) {
      ret = vm[hook].apply(vm, nargs) !== false && ret
    }
  })
  return ret
}

// 获取随机文件名
function getRandomFileName(fileExt) {
  return Array(32).fill(null).map(() => Math.round(Math.random() * 16).toString(16)).join('') + '.' + fileExt
}

// 获取文件后缀名
function getFileExt(fileFullName) {
  return fileFullName.split('.').pop()
}

// 文件是否支持
function isFileValid(extList, extName) {
  // 校验文件类型
  return extList.includes(extName)
}

/**
 * CRUD钩子
 */
OSS.HOOK = {
  /** 上传成功  */
  uploadSuccess: 'uploadOssSuccess',
  /** 上传失败 */
  uploadError: 'uploadOssError'
}

export default OSS

export { presenter }
